package com.wechatpay.v3.sdk;

import com.dayi.common.util.BeanMapUtils;
import com.dayi.common.util.Misc;
import com.dayi.common.util.Randoms;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wechatpay.v3.enums.RefundStatus;
import com.wechatpay.v3.enums.TradeState;
import com.wechatpay.v3.sdk.data.*;
import com.wechatpay.v3.vo.*;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import com.wechatpay.v3.constants.WechatPayResultCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 微信支付组件
 *
 * @author liuyi <liuy@pvc123.com>
 * @since 2021/7/23
 */
public class WechatPay {

    private final Logger logger = LoggerFactory.getLogger(WechatPay.class);

    private final WechatConfig wechatConfig;

    private final WechatPayRequest wechatPayRequest;

    ObjectMapper objectMapper = new ObjectMapper();

    public WechatPay(WechatConfig wechatConfig) {

        this.wechatConfig = wechatConfig;

        this.wechatPayRequest = new WechatPayRequest(this.wechatConfig, getPrivateKey());
    }

    /**
     * 统一下单接口
     * @param vo
     * @return
     */
    public UnifiedOrderResult unifiedOrder(UnifiedOrderVo vo) {
        // 忽略不存在的字段
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        String appId = StringUtils.isEmpty(vo.getAppId()) ? this.wechatConfig.getAppID() : vo.getAppId();
        String notifyUrl = StringUtils.isEmpty(vo.getNotifyUrl()) ? this.wechatConfig.getNotifyOrderCallbackUrl() : vo.getNotifyUrl();
        // 交易金额乘于100，转化成分
        BigInteger total = vo.getTotal().multiply(new BigDecimal("100")).toBigInteger();

        WechatPayRequestData wechatPayRequestData = new WechatPayRequestData(
                appId, this.wechatConfig.getMchID(), vo.getDescription(), vo.getTradeNo(), notifyUrl, total.intValue(), vo.getOpenId()
        );
        if (!Misc.isEmpty(vo.getAttach())) {
            wechatPayRequestData.setAttach(vo.getAttach());
        }

        try {
            WechatResult wechatResult;
            // 0 jsapi,1 app, 2 h5, 3 native
            switch (vo.getPayType()) {
                case 0: wechatResult = jsApi(wechatPayRequestData);break;
                case 1: wechatResult = app(wechatPayRequestData);break;
                case 2: return new UnifiedOrderResult(WechatPayResultCode.ERROR,"未实现");
                case 3: wechatResult = nativePay(wechatPayRequestData);break;
                default: return new UnifiedOrderResult(WechatPayResultCode.UNKNOW,"未知请求支付类型");
            }

            if (WechatPayResultCode.SUCCESS.equals(wechatResult.getCode())) {
                WechatPayResponseData wechatPayResponseData = objectMapper.readValue(wechatResult.getMessage(), WechatPayResponseData.class);
                UnifiedOrderResult unifiedOrderResult = createPayParams(appId,wechatPayResponseData.getPrepay_id());
                unifiedOrderResult.setCodeUrl(wechatPayResponseData.getCode_url());
                unifiedOrderResult.setH5Url(wechatPayResponseData.getH5_url());
                return unifiedOrderResult;
            } else {
                return objectMapper.readValue(wechatResult.getMessage(), UnifiedOrderResult.class);
            }
        } catch (Exception e) {
            logger.error("请求异常",e);
        }

        return new UnifiedOrderResult(WechatPayResultCode.ERROR,"请求异常");
    }

    /**
     * 查询支付状态
     * @param outTradeNo
     * @return
     */
    public WechatResult queryOrder(String outTradeNo) {
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        WechatResult wechatResult;
        try {
            wechatResult = wechatPayRequest.requestGet(WechatPayConstant.queryUrl + outTradeNo + "?mchid=" + this.wechatConfig.getMchID());


            if (WechatPayResultCode.SUCCESS.equals(wechatResult.getCode())) {
                WechatPayQueryResponseData wechatPayQueryResponseData = objectMapper.readValue(wechatResult.getMessage(), WechatPayQueryResponseData.class);
                if (Objects.equals(wechatPayQueryResponseData.getTrade_state(), "SUCCESS")) {
                    return new WechatResult(WechatPayResultCode.SUCCESS, "操作成功");
                }
                return new WechatResult(WechatPayResultCode.FAILURE, wechatPayQueryResponseData.getTrade_state_desc());
            }
            return objectMapper.readValue(wechatResult.getMessage(), WechatResult.class);
        } catch (Exception e) {
            logger.error("请求异常", e);
            return new WechatResult(WechatPayResultCode.ERROR, "请求异常");
        }
    }

    /**
     * 退款接口
     *
     * @param refundOrderVo
     */
    public RefundOrderResult refund(RefundOrderVo refundOrderVo) {
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        String url = WechatPayConstant.refundUrl;

        // 交易金额乘于100，转化成分
        BigInteger total = refundOrderVo.getTotal().multiply(new BigDecimal("100")).toBigInteger();
        BigInteger refund = refundOrderVo.getRefund().multiply(new BigDecimal("100")).toBigInteger();

        WechatRefundRequestData wechatRefundRequestData = new WechatRefundRequestData(
                refundOrderVo.getOutTradeNo(),refundOrderVo.getOutRefundNo(), refundOrderVo.getReason(), refundOrderVo.getNotifyUrl(), refund.intValue(), total.intValue()
        );

        try {
            String params = objectMapper.writeValueAsString(wechatRefundRequestData);
            WechatResult wechatResult = this.wechatPayRequest.requestPost(url,params);
            logger.info("wechat sdk refund message: {}",wechatResult.getMessage());
            if (WechatPayResultCode.SUCCESS.equals(wechatResult.getCode())) {
                WechatRefundResponseData re = objectMapper.readValue(wechatResult.getMessage(), WechatRefundResponseData.class);
                if (Objects.equals(re.getStatus(), RefundStatus.SUCCESS.key) || Objects.equals(re.getStatus(), RefundStatus.PROCESSING.key)) {
                    // 退款成功
                    return getRefundOrderResult(re);
                }
                return new RefundOrderResult(WechatPayResultCode.ERROR,RefundStatus.getRefundStatus(re.getStatus()).value);
            } else {
                return objectMapper.readValue(wechatResult.getMessage(), RefundOrderResult.class);
            }
        } catch (Exception e) {
            logger.error("退款失败",e);
        }
        return new RefundOrderResult(WechatPayResultCode.ERROR,"请求异常");
    }

    public WechatResult queryRefund(String outTradeNo) {
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        WechatResult wechatResult;
        try {
            wechatResult = wechatPayRequest.requestGet(WechatPayConstant.refundQueryUrl + outTradeNo);
            logger.info("wechat sdk refund query message: {}",wechatResult.getMessage());
            if (WechatPayResultCode.SUCCESS.equals(wechatResult.getCode())) {
                WechatRefundResponseData wechatRefundResponseData = objectMapper.readValue(wechatResult.getMessage(), WechatRefundResponseData.class);
                if (Objects.equals(wechatRefundResponseData.getStatus(), RefundStatus.SUCCESS.key)) {
                    return new WechatResult(WechatPayResultCode.SUCCESS, "操作成功");
                }
                return new WechatResult(wechatRefundResponseData.getStatus(), RefundStatus.getRefundStatus(wechatRefundResponseData.getStatus()).value);
            }
            return objectMapper.readValue(wechatResult.getMessage(), WechatResult.class);
        } catch (Exception e) {
            logger.error("请求异常", e);
            return new WechatResult(WechatPayResultCode.ERROR, "请求异常");
        }
    }

    /**
     * 关闭支付单
     * @param outTradeNo
     * @return
     */
    public WechatResult close(String outTradeNo) {

        Map<String, String> paramMap = new HashMap<>();

        paramMap.put("mchid", this.wechatConfig.getMchID());

        try {
             return wechatPayRequest.requestPost(WechatPayConstant.queryUrl + outTradeNo + "/close", objectMapper.writeValueAsString(paramMap));

        } catch (Exception e) {
            logger.error("请求异常", e);
            return new WechatResult(WechatPayResultCode.ERROR, "请求异常");
        }
    }

    /**
     * 支付回调 - 数据解密
     *
     * @param body 应答主体（response Body），需要按照接口返回的顺序进行验签，错误的顺序将导致验签失败
     * @return
     */
    public WechatResult payNotify(String body) {
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        String result;
        NotifyData notifyData = new NotifyData();
        try {
            WechatPayCallBackEncryptData data = objectMapper.readValue(body,WechatPayCallBackEncryptData.class);
            AesUtil aesUtil = new AesUtil(this.wechatConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));

            WechatPayCallBackEncryptData.Resource resource = data.getResource();
            String contents = aesUtil.decryptToString(resource.getAssociated_data().getBytes(StandardCharsets.UTF_8),resource.getNonce().getBytes(StandardCharsets.UTF_8),resource.getCiphertext());

            WechatNotifyData wechatNotifyData = objectMapper.readValue(contents,WechatNotifyData.class);

            if (!wechatNotifyData.getTrade_state().equals(TradeState.SUCCESS.name())) {
                return new WechatResult(WechatPayResultCode.FAILURE,"未支付成功通知");
            }
            notifyData.setAmount(new BigDecimal(wechatNotifyData.getAmount().getTotal()));
            notifyData.setAttach(wechatNotifyData.getAttach());
            notifyData.setOutTradeNo(wechatNotifyData.getOut_trade_no());

            result = objectMapper.writeValueAsString(notifyData);


        } catch (JsonProcessingException  e) {
            logger.error("解密失败,json序列化失败",e);
            return new WechatResult(WechatPayResultCode.ERROR,"解密失败,json序列化失败");
        } catch (RuntimeException e) {
            logger.error(e.getMessage(),e);
            return new WechatResult(WechatPayResultCode.ERROR,e.getMessage());
        } catch (GeneralSecurityException e) {
            logger.error("解密失败,安全性错误",e);
            return new WechatResult(WechatPayResultCode.FAILURE,"解密失败,安全性错误");
        } catch (IOException e) {
            logger.error("解密失败,IO错误",e);
            return new WechatResult(WechatPayResultCode.FAILURE,"解密失败,IO错误");
        }

        return new WechatResult(WechatPayResultCode.SUCCESS,result);
    }

    /**
     * 验签
     * @param wechatpaySerial HTTP请求头Wechatpay-Serial，微信支付平台证书的序列号，不参与加解密
     * @param wechatpaySignature HTTP请求头Wechatpay-Signature，使用Base64进行解码，得到应答签名
     * @param wechatpayTimestamp HTTP头Wechatpay-Timestamp 中的应答时间戳
     * @param wechatpayNonce HTTP头Wechatpay-Nonce 中的应答随机串
     * @param body 应答主体（response Body），需要按照接口返回的顺序进行验签，错误的顺序将导致验签失败
     * @return
     */
    public boolean responseSignVerify(String wechatpaySerial, String wechatpaySignature, String wechatpayTimestamp, String wechatpayNonce, String body) throws NoSuchAlgorithmException, InvalidKeyException {

        String signatureStr = buildMessage(wechatpayTimestamp, wechatpayNonce, body);

        return this.wechatPayRequest.getVerifier().verify(wechatpaySerial, signatureStr.getBytes(StandardCharsets.UTF_8),wechatpaySignature);
    }

    private WechatResult jsApi(WechatPayRequestData wechatPayRequestData) throws Exception {

        String url = WechatPayConstant.jsApiUrl;
        String params = objectMapper.writeValueAsString(wechatPayRequestData);
        return this.wechatPayRequest.requestPost(url,params);
    }

    private WechatResult app(WechatPayRequestData wechatPayRequestData) throws Exception {

        String url = WechatPayConstant.appUrl;

        Map map = new HashMap();
        BeanMapUtils.copy(wechatPayRequestData,map,"payer");

        String params = objectMapper.writeValueAsString(map);
        return this.wechatPayRequest.requestPost(url,params);
    }

    private WechatResult nativePay(WechatPayRequestData wechatPayRequestData) throws Exception {

        String url = WechatPayConstant.nativeUrl;

        Map map = new HashMap();
        BeanMapUtils.copy(wechatPayRequestData,map,"payer");

        String params = objectMapper.writeValueAsString(map);
        return this.wechatPayRequest.requestPost(url,params);
    }

    /**
     * 组装前端唤起支付参数
     *
     * @param prepayId 预下单接口返回数据 预支付交易会话标识prepay_id
     * @return
     */
    private UnifiedOrderResult createPayParams(String appId, String prepayId) {

        String nonceStr = Randoms.genRandomString(8);
        long timestamp = System.currentTimeMillis() / 1000;
        String message = buildMessage(appId,timestamp, nonceStr, prepayId);
        String signature = sign(message.getBytes(StandardCharsets.UTF_8));

        UnifiedOrderResult unifiedOrderResult = new UnifiedOrderResult();
        unifiedOrderResult.setPrepayId(prepayId);
        unifiedOrderResult.setAppId(appId);
        unifiedOrderResult.setNonceStr(nonceStr);
        unifiedOrderResult.setTimestamp(Long.toString(timestamp));
        unifiedOrderResult.setPaySign(signature);

        return unifiedOrderResult;
    }

    private RefundOrderResult getRefundOrderResult(WechatRefundResponseData refundResponseData) {

        return new RefundOrderResult(
                refundResponseData.getRefund_id(),
                refundResponseData.getOut_refund_no(),
                refundResponseData.getTransaction_id(),
                refundResponseData.getOut_trade_no(),
                refundResponseData.getChannel(),
                refundResponseData.getUser_received_account(),
                refundResponseData.getStatus(),
                new BigDecimal(refundResponseData.getAmount().getRefund()).divide(new BigDecimal("100"),2)
        );

    }
    /**
     * 微信支付-前端唤起支付参数-签名
     * @param message 签名数据
     * @return
     */
    private String sign(byte[] message) {
        try{
            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initSign(getPrivateKey());
            sign.update(message);
            return Base64.getEncoder().encodeToString(sign.sign());
        } catch(Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * 微信支付-前端唤起支付参数-构建签名参数
     * @param nonceStr 签名数据
     * @return
     */
    private String buildMessage(String appId, long timestamp, String nonceStr, String packageStr) {
        return appId + "\n"
                + timestamp + "\n"
                + nonceStr + "\n"
                + packageStr + "\n";
    }

    /**
     * 回调验签-构建签名数据
     *
     * @return
     */
    public static String buildMessage(String wechatpayTimestamp, String wechatpayNonce, String body) {
        return wechatpayTimestamp + "\n"
                + wechatpayNonce + "\n"
                + body + "\n";
    }

    private PrivateKey getPrivateKey() {

        InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(this.wechatConfig.getCertFile());
        if(null == inputStream) {
            throw new RuntimeException("File "+this.wechatConfig.getCertFile()+" NotFound");
        }
        return PemUtil.loadPrivateKey(inputStream);
    }


}
